package cn.studyjava.verify.service.impl;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.UUID;

import javax.imageio.ImageIO;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

import cn.studyjava.verify.common.Const;
import cn.studyjava.verify.model.ImagePoint;
import cn.studyjava.verify.model.ImageVerify;
import cn.studyjava.verify.service.IGenerateCodeService;
import cn.studyjava.verify.util.Base64Util;
import cn.studyjava.verify.util.JsonUtil;
import net.coobird.thumbnailator.Thumbnails;
import net.coobird.thumbnailator.Thumbnails.Builder;
import net.coobird.thumbnailator.geometry.Coordinate;
import net.coobird.thumbnailator.geometry.Position;

/**
 * <p>
 * Description:
 * </p>
 * 
 * @author: zsljava
 * @version 1.0.0
 * <p>
 * History:
 * -----------------------------------------------
 * @Date: 2017年10月18日 下午2:30:40
 * @author: zsljava
 * @version 1.0.0
 * @OP: Create
 * -----------------------------------------------
 * </p>
 *
 * @since
 * @see
 */
@Service
public class GenerateCodeServiceImpl implements IGenerateCodeService {
	
	private Logger log = LoggerFactory.getLogger(GenerateCodeServiceImpl.class);

	/**
	 * 重新初始化目录，会将resource/defverify目录下的图片覆盖base目录	
	 */
	@Value("${folder.initDir}")
	private boolean initDir;
	
	/**
	 * base目录
	 */
	@Value("${folder.baseDir}")
	private String baseDir;
	
	/**
	 * 原图目录
	 */
	@Value("${folder.artworkDir}")
	private String artworkDir;
	
	/**
	 * 原修复图的目录
	 */
	@Value("${folder.repairDir}")
	private String repairDir;
	
	@Autowired
	private StringRedisTemplate redisTemplate;
	
	/**
	 * 传入个数 生成随机验证码
	 */
	@Override
	public void generate(Integer size) {
		log.debug("开始生成验证码共 {} 个", size);
		long startTime = System.currentTimeMillis() / 1000;
		size = size == null ? Const.DEFAULT_GENERATE_SIZE : size;
		// 获取原图目录中的图片
		File artworkDirs = new File(baseDir.concat(artworkDir));
		File[] artworkFiles = artworkDirs.listFiles();
		int artworkDirLen = artworkFiles.length;
		
		//获取补图目录中的图片
		File repairDirs = new File(baseDir.concat(repairDir));
		File[] orgRepairFiles = repairDirs.listFiles();
		int orgRepairLen = orgRepairFiles.length;
		
		int len = 0;
		int len2 = 0;
		Random random = new Random();
		for (int i = 0; i < size ; i++) { // 随机组合
			if(artworkDirLen > 0){
				len = random.nextInt(artworkDirLen) % (artworkDirLen + 1);
			}
			if(orgRepairLen > 0 ){
				len2 = random.nextInt(orgRepairLen) % (orgRepairLen + 1);
			}
			generate(artworkFiles[len].getPath(),orgRepairFiles[len2].getPath());
		}
		Long endTime = System.currentTimeMillis() / 1000 - startTime;
		log.debug("{} 个验证码生成成功，用时 {} 秒,每秒 {} 张", size, endTime, size / endTime);
	}
	
	/**
	 * 重载generate用原图 和补图生成一张图片
	 * @param originalUrl
	 * @param alphaUrl
	 */
	public void generate(String originalUrl, String alphaUrl){
		int width = 300;   // 验证码图片宽度
		int height = 150;  // 验证码图片高度
		int repairWidth = 50; // 修复图宽度
		int repairHeight = 50; // 修复图高度
		int wholeItemWidth = 30; // 打乱修复图的宽度
		int wholeItemHeight = 15; // 打乱修复图高度
		try {
			generate(originalUrl, alphaUrl, width, height, repairWidth, repairHeight, wholeItemWidth, wholeItemHeight);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 *  核心方法生成图片验证码
	 * @param originalUrl
	 * @param alphaUrl
	 * @param width
	 * @param height
	 * @param repairWidth
	 * @param repairHeight
	 * @param wholeItemWidth
	 * @param wholeItemHeight
	 * @throws Exception
	 */
	private void generate(String originalUrl,String alphaUrl
						,int width,int height
						,int repairWidth,int repairHeight
						,int wholeItemWidth,int wholeItemHeight) throws Exception{
		if(width % wholeItemWidth != 0 
				|| height % wholeItemHeight != 0){
			throw new RuntimeException("破图图形错误!");
		}
		Random random = new Random();
		// 获取随机位置范围
		int xPosition = random.nextInt(width-repairWidth) % (width-repairWidth*2 + 1) + repairWidth;
//		int y_ = random.nextInt(height-repairHeight) % (height-repairHeight*2 + 1) + repairHeight;
		int yPosition = random.nextInt(height-repairHeight) % (height-repairHeight + 1);
		
		ImageVerify verify = new ImageVerify();
//		verify.setInValid(DateUtils.addMinutes(new Date(), 10));
		verify.setCreateTime(new Date());
		verify.setOriginal(originalUrl);
		verify.setOffset(10);
		verify.setX(xPosition);
		verify.setY(yPosition);
		verify.setVerifyCode(UUID.randomUUID().toString());
		verify.setRepairWidth(repairWidth);
		verify.setRepairHeight(repairHeight);
		
		// 生产破图
		// 1.产生破图层
		Builder<File> alpha = Thumbnails.of(alphaUrl).size(repairWidth, repairHeight).keepAspectRatio(true);
		
		Position position = new Coordinate((int)verify.getX(), (int)verify.getY());
		BufferedImage wholeImg = Thumbnails.of(verify.getOriginal())
									// 透明度 
									.watermark(position, alpha.asBufferedImage(), 0.6f) 
									//生成质量100%  
							        .outputQuality(1)
							        //缩放比例 
							        .scale(1)
							        .asBufferedImage();
		// 2.打乱破图层（图片会被打乱需要用point坐标去还原）
		int x = wholeItemWidth;
		int y = wholeItemHeight;
		int itemWidth = width/x;
		int itemHeight = height/y;
		int currentX = 0;
		int currentY = 0;
		ImagePoint point = null;
		BufferedImage item = null;
		List<ImagePoint> images = new ArrayList<ImagePoint>();
		for (int h = 0;h < y ; h++) {
			for(int w = 0;w < x; w++){
				point = new ImagePoint();
				point.setX(currentX);
				point.setY(currentY);
				item = Thumbnails.of(wholeImg).sourceRegion(currentX,currentY, itemWidth, itemHeight)
							.size(itemWidth, itemHeight).keepAspectRatio(true).asBufferedImage();
				point.setImage(item);
				currentX += itemWidth;
				images.add(point);
			}
			currentX = 0;
			currentY = itemHeight * ( h+1 );
		}
		Collections.shuffle(images);
		currentX = 0;
		currentY = 0;
		JsonObject object = new JsonObject();
		object.addProperty("width", width);
		object.addProperty("height", height);
		object.addProperty("itemWidth", itemWidth);
		object.addProperty("itemHeight", itemHeight);
		JsonArray array = new JsonArray();
		BufferedImage imageNew = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
		int[] imageArray = null;
		JsonObject element = null;
		// 原图宽高
		for (int h = 0;h < y ; h++) { 
			// 打乱后小图宽高
			for(int w = 0;w < x; w++){ 
				point = images.get(0);
				element = new JsonObject();
				element.addProperty("x", point.getX());
				element.addProperty("y", point.getY());
				images.remove(0);
				imageArray = new int[itemWidth*itemHeight];
				imageArray = point.getImage().getRGB(0,0,itemWidth,itemHeight,imageArray,0,itemWidth);
				//设置右半部分的RGB
				imageNew.setRGB(currentX,currentY,itemWidth,itemHeight,imageArray,0,itemWidth);
				currentX += itemWidth;
				array.add(element);
			}
			currentX = 0;
			currentY = itemHeight * ( h+1 );
		}
		object.add("point", array);
		// 得到破图的坐标
		verify.setPoint(object.toString());			
		

		// 3.保存破图图片
	    ByteArrayOutputStream out = new ByteArrayOutputStream();
	    ImageIO.write(imageNew, "png", out);		
	    String code = Base64Util.encodeImg(out.toByteArray());
	    verify.setWholeImg(code);
	    out.flush();
	    out.close();

	    // 4.保存补图
	    ByteArrayOutputStream repairOut = new ByteArrayOutputStream();
	    Thumbnails.of(verify.getOriginal()).sourceRegion((int)verify.getX(),(int)verify.getY(),repairWidth, repairHeight)
	    			.size(repairWidth, repairHeight).keepAspectRatio(true).toOutputStream(repairOut);
	    code = Base64Util.encodeImg(repairOut.toByteArray());
	    verify.setRepairImg(code);
	    repairOut.flush();
	    repairOut.close();
	    
	    redisTemplate.opsForSet().add(Const.VERIFY_CODE_SET, JsonUtil.obj2json(verify));
		
	}
}
